/*
 * ******************************************************************************
 * Class: Register	
 * 
 * @author: Teresa Mafalda dos Reis Frazao
 * @version:April 10, 2014 
 * 
 * This class is responsible for persisting service objects. Thus, it includes 
 * two ConcurrentHashMap one for storing the ServiceNode objects and other for 
 * keeping their state.
 * This class is the only modifier of these data structures, so it works as a 
 * registry manager.   
 * ******************************************************************************
 */
package com.feedzai.service.utils;

import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;

import com.feedzai.service.data.ServiceNode;



/**
 * The Class Register.
 */
public class Register {

	
	/** The Constant ZERO. */
	private static final int ZERO = 0;
	
	
	/** The service node hash map. 
	 * The Key is service <code>id</code>, and the value the <code>service</code> itself.*/
	private static ConcurrentHashMap<Long, ServiceNode> serviceNodeHashMap;
	
	/** The service node status hash map. 
	 * Value is <code>true</code> if the node is running.
	 * Otherwise the value is <code>false</code>.*/
	private static ConcurrentHashMap<Long, Boolean> serviceNodeStatusHashMap;
	
	
	/**
	 * Instantiates a new dependency list utils.
	 */
	public Register() {

		// Initialize data structures managed by Service Manager
		serviceNodeHashMap = new ConcurrentHashMap<Long, ServiceNode>();
		serviceNodeStatusHashMap = new ConcurrentHashMap<Long, Boolean>();
	}
	
	
	/*
	 * ---------------------------------------------------------------------------
	 * 							Public Interface 
	 * ---------------------------------------------------------------------------
	 */
	
	
	/**
	 * Gets all the services.
	 *
	 * @return the available services
	 */
	public Collection<ServiceNode> getAvailableServices() 
	{
		return serviceNodeHashMap.values();
	}
	
	
	/**
	 * Gets the a single service node.
	 *
	 * @param serviceId the service id
	 * @return the a service node
	 */
	public ServiceNode getAServiceNode(Long serviceId)
	{
		// The ID has to be a natural number and node cannot be null
		if(serviceId == null || serviceId < ZERO)
		{
			throw new IllegalArgumentException("ID is not valid.");
		}
		else
		{
			ServiceNode serviceNode = serviceNodeHashMap.get(serviceId);
			return serviceNode;
		}
	}
	
	
	/**
	 * Gets the a service node status.
	 *
	 * @param serviceId the service id
	 * @return the a service node status
	 */
	public Boolean getAServiceNodeStatus(Long serviceId)
	{
		// The ID has to be a natural number and node cannot be null
		if(serviceId == null || serviceId < ZERO)
		{
			throw new IllegalArgumentException("ID is not valid, so I cannot return a status.");
		}
		else
		{
			Boolean serviceNode = serviceNodeStatusHashMap.get(serviceId);
			return serviceNode;
		}
	}
	
	
	/**
	 * Adds the new service. To add a new service we must:
	 * <p>1. Ensure the service is added to <code>serviceNodeHashMap</code>
	 * <p>2. Then ensure that its status is added to <code>serviceNodeStatusHashMap</code>
	 * <p>3. If some error occurs me must undo the changes, so no service is created.
	 *
	 * @param serviceNode the service node
	 * @return true, if the service was successfully created 
	 */
	public boolean addNewService(ServiceNode serviceNode)
	{
		boolean isServiceAddedToServiceNodeHashMap = addNewServiceToServiceNodeHashMap(serviceNode);
		
		// 
		// If we are unable to add a new service we should stop
		//
		if(!isServiceAddedToServiceNodeHashMap)
			return false;
		
		else
		{
			boolean isServiceAddedToServiceNodeStatusHashMap = addNewServiceToServiceNodeStatusHashMap(serviceNode.getId());
			
			//
			// If there is a problem with the status we need to 
			// 1) undo the entire add service operation 
			// 2) return false
			//
			if(!isServiceAddedToServiceNodeStatusHashMap)
			{
				// We don't care about the result, since the function returns false if the service node does not exist.
				removeServiceToServiceNodeHashMap(serviceNode.getId());
				return false;
			}
			
			// If we reach this state we are able to finish the operation by returning true.
			else
			{
				return true;
			}
		}
	}
	
	
	/**
	 * Destroys data structures used for test purposes.
	 */
	public void destroyRegisters() 
	{
		serviceNodeHashMap = null;
		serviceNodeStatusHashMap = null;
	}
	
	/**
	 * Creates data structures used for test purposes.
	 */
	public void createRegisters() {

		// Initialize data structures managed by Service Manager
		serviceNodeHashMap = new ConcurrentHashMap<Long, ServiceNode>();
		serviceNodeStatusHashMap = new ConcurrentHashMap<Long, Boolean>();
	}
	
	
	/*
	 * ---------------------------------------------------------------------------
	 * 							Private Interface 
	 * ---------------------------------------------------------------------------
	 */
	
	
	/**
	 * Adds the new service to service node hash map.
	 * 
	 * <p>We must ensure there are no duplicates, if so we return <code>false</code>. 
	 *
	 * @param serviceNode the service node
	 * @return true, if service was successfully registered in <code>serviceNodeHashMap</code>
	 */
	private boolean addNewServiceToServiceNodeHashMap(ServiceNode serviceNode)
	{
		// The ID has to be a natural number and node cannot be null
		if(serviceNode == null || serviceNode.getId() == null || serviceNode.getId().longValue() < ZERO)
		{
			throw new IllegalArgumentException("The node is null or de ID is not valid, so cannot register service.");
		}
		else
		{
			//
			// We should not create services that are already created. Thus, avoid duplicates
			//
			if(serviceNodeHashMap.containsKey(serviceNode.getId()))
				return false;
			
			// We registered the new service
			serviceNodeHashMap.put(serviceNode.getId(), serviceNode);
			return true;
		}
	}
	
	
	/**
	 * Adds the new service to service node status hash map.
	 * 
	 * <p>We must ensure there are no duplicates, if so we return <code>false</code>. 
	 *
	 * @param serviceId the service id
	 * @return true, if service was successfully registered in <code>serviceNodeStatusHashMap</code>
	 */
	private boolean addNewServiceToServiceNodeStatusHashMap(Long serviceId)
	{
		// The ID has to be a natural number and node cannot be null
		if(serviceId == null || serviceId < ZERO)
		{
			throw new IllegalArgumentException("The node is null or de ID is not valid, so I cannot include a status.");
		}
		else
		{
			//
			// When a service is created the status is still false
			// Thus, after running the service it is possible to set its status to true
			// This way we avoid having services that are created but cannot start
			//
			if(serviceNodeStatusHashMap.containsKey(serviceId))
				return false;
			
			// We registered the new service
			serviceNodeStatusHashMap.put(serviceId, false);
			return true;
		}
	}
	
	
	/**
	 * Switch Service node status. Thus, set it to <code>false</code> if its <code>true</code>.
	 * Otherwise set it to <code>true</code> if its <code>false</code>.
	 *
	 * @param serviceId the service id
	 * @return true, if successful
	 */
	public boolean switchNodeStatus(Long serviceId)
	{
		// The ID has to be a natural number and node cannot be null
		if(serviceId == null || serviceId < ZERO)
		{
			throw new IllegalArgumentException("The node is null or de ID is not valid. So I cannot updater status");
		}
		else
		{
			//
			// We should not update services that do not exist
			//
			if(!serviceNodeStatusHashMap.containsKey(serviceId))
				return false;
			
			else
			{
				//
				// Since we are writing using multiple threads use synchronized clause 
				//
				synchronized (serviceNodeStatusHashMap) {
					// Set the running status to its NOT value
					boolean status = serviceNodeStatusHashMap.get(serviceId);			
					serviceNodeStatusHashMap.put(serviceId, !status);
					
					return true;
				}
			}
		}
	}
	
	
	/**
	 * Update a service.
	 * Thus, it replaces the current service by a new one or ads a new record in <code>serviceNodeHashMap</code>.
	 *
	 * @param serviceNode the service node
	 */
	public void updateService(ServiceNode serviceNode)
	{
		// The ID has to be a natural number and node cannot be null
		if(serviceNode.getId() == null || serviceNode.getId() < ZERO)
		{
			throw new IllegalAccessError("The service has no valid ID");
		}
		
		synchronized (serviceNodeHashMap) {
			serviceNodeHashMap.put(serviceNode.getId(), serviceNode);
		}
	}
	
	
	/**
	 * Removes the service to service node hash map.
	 * This method should not be called, unless the service was not created successfully.
	 * We don't remove services, since services exist forever, they only start and stop.   
	 *
	 * @param serviceNodeId the service node id
	 * @return true, if successful
	 */
	private boolean removeServiceToServiceNodeHashMap(Long serviceNodeId)
	{
		// The ID has to be a natural number and node cannot be null
		if(serviceNodeId == null || serviceNodeId.longValue() < ZERO)
		{
			throw new IllegalArgumentException("The node is null or de ID is not valid");
		}
		else
		{
			//
			// If the services do not exist we are unable to remove them
			//
			if(!serviceNodeHashMap.containsKey(serviceNodeId))
				return false;
			
			// We removed the service successfully
			serviceNodeHashMap.remove(serviceNodeId);
			return true;
		}
	}
}
